package fr.inserm.umr915.vcfserver.j2ee;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

import fr.inserm.umr915.vcfserver.bio.GeneticCode;
import fr.inserm.umr915.vcfserver.bio.ReferenceSequence;
import fr.inserm.umr915.vcfserver.bio.Segment;
import fr.inserm.umr915.vcfserver.bio.berkeley.DatabaseId;
import fr.inserm.umr915.vcfserver.bio.berkeley.Storage;
import fr.inserm.umr915.vcfserver.bio.segparser.SegmentParser;
import fr.inserm.umr915.vcfserver.bio.ucsc.knownGene.KnownGene;
import fr.inserm.umr915.vcfserver.bio.ucsc.knownGene.KnownGeneHandler;
import fr.inserm.umr915.vcfserver.lang.LoopControl;

@SuppressWarnings("serial")
public class VariationPredictionServlet
	extends HttpServlet
	{
	/** thread safe genomic sequence */
	private GenomicSeq threadSafeGenomicSeq=null;
	/**
	 * AbstractCharSequence
	 */
	private static abstract class AbstractCharSequence
		implements CharSequence
		{
		@Override
		public abstract char charAt(int index);
		@Override
		public abstract int length();
		@Override
		public CharSequence subSequence(int start, int end)
			{
			StringBuilder s=new StringBuilder(end-start);
			while(start<end)
				{
				s.append(charAt(start));
				++start;
				}
			return s;
			}
		@Override
		public String toString()
			{
			return subSequence(0,length()).toString();
			}
		}
	
	private static class GenomicSeq
		extends AbstractCharSequence
		{
		private byte[] array;
		private int chromStart;
		GenomicSeq(byte array[],int chromStart)
			{
			this.array=array;
			this.chromStart=chromStart;
			}
		
		public int getChromStart()
			{
			return this.chromStart;
			}
		public int getChromEnd()
			{
			return getChromStart()+this.array.length;
			}
		@Override
		public char charAt(int index)
			{
			if(index<getChromStart() || index>=getChromEnd()) return '?';
			return Character.toUpperCase((char)this.array[index-getChromStart()]);
			}
		@Override
		public int length()
			{
			return getChromEnd();
			}
		}
	
	private static class ProteinCharSequence
		extends AbstractCharSequence
		{
		private GeneticCode code;
		private CharSequence delegate;
		public ProteinCharSequence(GeneticCode code,CharSequence delegate)
			{
			this.code=code;
			this.delegate=delegate;
			}
		
		@Override
		public char charAt(int index)
			{
			return this.code.translate(
				delegate.charAt(index*3+0),
				delegate.charAt(index*3+1),
				delegate.charAt(index*3+2)	
				);
			}
		
		@Override
		public int length()
			{
			return delegate.length()/3;
			}
		}
	
	
	private static class MutedSequence
		extends AbstractCharSequence
		{
		private CharSequence delegate;
		private Map<Integer,Character> mutations=new HashMap<Integer, Character>();
		public MutedSequence(CharSequence delegate)
			{
			this.delegate=delegate;
			}
		@Override
		public char charAt(int index)
			{
			Character c= mutations.get(index);
			return c==null?delegate.charAt(index):c;
			}
		@Override
		public int length()
			{
			return delegate.length();
			}
		}
	
	
	
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException
		{
		doGet(req, resp);
		}
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException
		{
		Document dom=null;
		try
			{
			//Path path=new Path(req.getPathInfo());
			WebApplicationContext factory = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
			
			//if(chromosome==null) throw new IllegalArgumentException("Cannot find chromosome:\""+path.get(1)+"\"");
			
			String segment=req.getParameter("segment");
			if(segment==null) segment=req.getParameter("pos");
			if(segment==null) segment=req.getParameter("position");
			if(segment==null) segment=req.getParameter("loc");
			if(segment==null) throw new ServletException("Parameter 'pos' missing");
			Segment seg=SegmentParser.parseOne(segment);
			if(seg.getChromStart()+1!=seg.getChromEnd())
				{
				throw new ServletException("chromStart+1!=chromEnd in "+seg);
				}
			
			
			String ref= req.getParameter("ref");
			String alts= req.getParameter("alt");
			if(alts==null) throw new IllegalArgumentException("Parameter 'alt' missing");
			alts=alts.toUpperCase();
			if(ref!=null) ref=ref.toUpperCase();
			dom=predic(req,resp,factory,seg,ref,alts);
			}
		catch(Exception err)
			{
			throw new ServletException(err);
			}
		
		try
			{
			OutputStream out=resp.getOutputStream();
			TransformerFactory factory=TransformerFactory.newInstance();
			Transformer transformer=factory.newTransformer();
			String encoding=resp.getCharacterEncoding();
			if(encoding==null) encoding="UTF-8";
			transformer.setOutputProperty(OutputKeys.ENCODING,encoding);
			
			resp.setContentType("text/xml");
			transformer.transform(
					new DOMSource(dom),
					new StreamResult(out)
					);
			out.flush();
			out.close();
			}
		catch (Exception err)
			{
			throw new ServletException(err);
			}
		}
	
	private Document predic
		(
		HttpServletRequest req,
		HttpServletResponse resp,
		BeanFactory beanFactory,
		Segment seg0,
		String refBase,
		String alts
		) throws ServletException,IOException
		{
		boolean insert_gene_structure=true;
		boolean insert_sequences=true;
		if("false".equals(req.getParameter("structure")))
			{
			insert_gene_structure=false;
			}
		if("false".equals(req.getParameter("sequence")))
			{
			insert_sequences=false;
			}
		
		Storage storage=(Storage)this.getServletContext().getAttribute("berkeley.db.environment");
		
		Document dom=null;
		try {
			DocumentBuilderFactory f=DocumentBuilderFactory.newInstance();
			f.setCoalescing(true);
			f.setNamespaceAware(false);
			f.setValidating(false);
			f.setExpandEntityReferences(true);
			f.setIgnoringComments(false);
			f.setIgnoringElementContentWhitespace(true);
			dom= f.newDocumentBuilder().newDocument();
		} catch (ParserConfigurationException e)
			{
			throw new ServletException(e);
			}
		String chromosome=seg0.getChromosome();
		int position0=seg0.getChromStart();
		
		KnownGene.AsDom toDom=new KnownGene.AsDom();
		KnownGeneHandler manager=(KnownGeneHandler)storage.getNameToMap().get("knownGene");
		if(manager==null) throw new ServletException();
		
		GenomicSeq localGenomicSeq=null;
		Integer longest= storage.getLongest(manager.getId());
		if(longest==null) throw new ServletException();
		if(longest<50000) longest=50000;//TODO
		DatabaseEntry key=new DatabaseEntry();
		DatabaseEntry data=new DatabaseEntry();
		DatabaseEntry entry3=new DatabaseEntry();
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		ReferenceSequence refseq=(ReferenceSequence)beanFactory.getBean("genome.reference");
		
		List<KnownGene> knownGenes=new ArrayList<KnownGene>();
		Cursor cursor=null;
		
		Segment seg2 =new Segment(chromosome,
				Math.max(position0-longest,0),
				position0+1);
		segmentBinding.objectToEntry(seg2,key);
			
			try
			{
			boolean first=true;
			cursor= storage.loc2id.openCursor(null,null);
			for(;;)
				{
				OperationStatus status;
				if(first)
					{
					status=cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
					}
				else
					{
					status=cursor.getNext(key, data, LockMode.DEFAULT);
					}
				first=false;
				
				if(status!=OperationStatus.SUCCESS) break;
				
				
				seg2= segmentBinding.entryToObject(key);
				LoopControl ctrl=Segment.scanner(seg0, seg2);
				if(ctrl==LoopControl.BREAK)
					{
					//System.err.println("break:"+seg2+" "+seg0+" long:"+longest);
					break;
					}
				if(ctrl==LoopControl.CONTINUE)
					{
					//System.err.println("cont:"+seg2+" "+seg0+" long:"+longest);
					continue;
					}
				
				
				DatabaseId dbId=dbidBinding.entryToObject(data);
				if(dbId.getDatabase()!=manager.getId()) continue;
				
				
				status=storage.id2object.get(null, data, entry3, LockMode.DEFAULT);
				if(status!=OperationStatus.SUCCESS)
					{
					getServletContext().log("uh????"+dbId);
					continue;
					}
				
				
				KnownGene kg=manager.getDataBinding().entryToObject(entry3);
				knownGenes.add(kg);
				}
			}
		catch(Exception err)
			{
			throw new ServletException();
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
			
			
			
			Element root=dom.createElement("Variations");
			dom.appendChild(root);
			Set<String> altBases=new HashSet<String>();  
			for(String alt:alts.split("[,]"))
				{
				if(alt.isEmpty()) continue;
				if(refBase==null)
					{
					//ignore
					}
				else if(refBase.equals("A"))
	    			{
	    				 if(alt.equals("W")) { alt="T"; }
	    			else if(alt.equals("M")) { alt="C"; }
	    			else if(alt.equals("R")) { alt="G"; }
	    			}
	    		else if(refBase.equals("C"))
	    			{
	    				 if(alt.equals("S")) { alt="G"; }
	    			else if(alt.equals("M")) { alt="A"; }
	    			else if(alt.equals("Y")) { alt="T"; }
	    			}
	    		else if(refBase.equals("G"))
	    			{
	    				 if(alt.equals("S")) { alt="C"; }
	    			else if(alt.equals("K")) { alt="T"; }
	    			else if(alt.equals("R")) { alt="A"; }
	    			}
	    		else if(refBase.equals("T"))
	    			{
	    				 if(alt.equals("W")) { alt="A"; }
	    			else if(alt.equals("K")) { alt="G"; }
	    			else if(alt.equals("Y")) { alt="C"; }
	    			}
				if(refBase!=null && refBase.equals(alt)) continue;
				altBases.add(alt);
				}
			root.appendChild(dom.createComment("count genes := "+knownGenes.size()));
			
			for(String alt:altBases)
				{
				Element var=dom.createElement("Variation");
				root.appendChild(var);
				var.setAttribute("chrom",chromosome);
				var.setAttribute("position",String.valueOf(position0));
				if(refBase!=null) var.setAttribute("ref",refBase);
				var.setAttribute("alt",alt);
					
					
					
				for(KnownGene gene:knownGenes)
						{
					
						Element transcript=dom.createElement("transcript");
						var.appendChild(transcript);
						if(insert_gene_structure)
							{
							transcript.appendChild(toDom.toDom(gene, dom));
							}	
			           
			
		        		
		            	if( (refBase==null || refBase.equals("A") || refBase.equals("T") || refBase.equals("G") || refBase.equals("C")) &&
		            		(alt.equals("A") || alt.equals("T") || alt.equals("G") || alt.equals("C"))
		            		)
			        		{
		            		GeneticCode geneticCode=GeneticCode.getByChromosome(chromosome);
			        		StringBuilder wildRNA=null;
			        		ProteinCharSequence wildProt=null;
			        		ProteinCharSequence mutProt=null;
			        		MutedSequence mutRNA=null;
			        		int position_in_cdna=-1;
			        		
			        		synchronized (this)
			        			{
				        		if(this.threadSafeGenomicSeq==null ||
				        	     !(this.threadSafeGenomicSeq.getChromStart()<=gene.getTxStart() && gene.getTxEnd() <= this.threadSafeGenomicSeq.getChromEnd())
				        	        )
			    	            	{
			    	            	int start=Math.max(gene.getTxStart()-100,0);
			    	            	
			    	            	
			    	            	this.threadSafeGenomicSeq=new GenomicSeq(
			    	            			refseq.getSequence(
			    	            					chromosome,
			    	            					start,//my api is 0-based
			    	            					gene.getTxEnd()+100
			    	            					),
			    	            			start
			    	            			);
			    	            	}
				        		localGenomicSeq=this.threadSafeGenomicSeq;
			        			}
			        		if(refBase!=null && !String.valueOf(localGenomicSeq.charAt(position0)).equalsIgnoreCase(refBase))
			        			{
			        			transcript.appendChild(dom.createComment("WARNING REF!=GENOMIC SEQ!!! at "+localGenomicSeq.charAt(position0)+"/"+refBase));
			        			}
			        		
			        		if(gene.isForward())
			            		{
			            		if(position0 < gene.getCdsStart())
			            			{
			            			append(transcript,"type", "UTR5");
			            			}
			            		else if( gene.getCdsEnd()<= position0 )
			            			{
			            			append(transcript,"type", "UTR3");
			            			}
			            		else
				            		{
				            		int exon_index=0;
				            		while(exon_index< gene.getExonCount())
				            			{
				            			KnownGene.Exon exon= gene.getExon(exon_index);
				            			for(int i= exon.getStart();
				            					i< exon.getEnd();
				            					++i)
				            				{
				            				if(i==position0)
				        						{
				        						append(transcript,"exon.name", exon.getName());
				        						}
				            				if(i< gene.getCdsStart()) continue;
				            				if(i>=gene.getCdsEnd()) break;
				        					
				        					if(wildRNA==null)
				        						{
				        						wildRNA=new StringBuilder();
				        						mutRNA=new MutedSequence(wildRNA);
				        						}
				        					
				        					if(i==position0)
				        						{
				        						append(transcript,"type", "EXON");
				        						append(transcript,"exon.name",exon.getName());
				        						position_in_cdna=wildRNA.length();
				        						append(transcript,"pos.cdna", String.valueOf(position_in_cdna));
				        						//in splicing ?
				        						if(exon.isSplicing(position0))
				        							{
				        							append(transcript,"splicing", "SPLICING");
				        							
				        							if(exon.isSplicingAcceptor(position0))
				        								{
				        								append(transcript,"splicing", "SPLICING_ACCEPTOR");
				        								}
				        							else  if(exon.isSplicingDonor(position0))
				        								{
				        								append(transcript,"splicing", "SPLICING_DONOR");
				        								}
				        							}
				        						}
				        					
				            				wildRNA.append(localGenomicSeq.charAt(i));
				            				
				            				if(i==position0)
				            					{
				            					mutRNA.mutations.put(
				            							position_in_cdna,
				            							alt.charAt(0)
				            							);
				            					}
				            				
				            				if(wildRNA.length()%3==0 && wildRNA.length()>0 && wildProt==null)
					            				{
					            				wildProt=new ProteinCharSequence(geneticCode,wildRNA);
					            				mutProt=new ProteinCharSequence(geneticCode,mutRNA);
					            				}
				            				}
				            			KnownGene.Intron intron= exon.getNextIntron();
				            			if(intron!=null && intron.contains(position0))
				            				{
				            				append(transcript,"intron.name",intron.getName());
				            				append(transcript,"type", "INTRON");
				            				
				            				if(intron.isSplicing(position0))
				        						{
				            					append(transcript,"splicing", "INTRON_SPLICING");
				        						if(intron.isSplicingAcceptor(position0))
				        							{
				        							append(transcript,"splicing", "INTRON_SPLICING_ACCEPTOR");
				        							}
				        						else if(intron.isSplicingDonor(position0))
				        							{
				        							append(transcript,"splicing", "INTRON_SPLICING_DONOR");
				        							}
				        						}
				            				}
				            			++exon_index;
				            			}
				            		}
			            		
			            		
			            		}
			            	else // reverse orientation
			            		{
			            	
			            		if(position0 < gene.getCdsStart())
			            			{
			            			append(transcript,"type", "UTR3");
			            			}
			            		else if( gene.getCdsEnd()<=position0 )
			            			{
			            			append(transcript,"type", "UTR5");
			            			}
			            		else
				            		{
				            		int exon_index = gene.getExonCount()-1;
				            		while(exon_index >=0)
				            			{
				            			KnownGene.Exon exon= gene.getExon(exon_index);
				            			for(int i= exon.getEnd()-1;
				            				    i>= exon.getStart();
				            				--i)
				            				{
				            				if(i==position0)
				        						{
				            					append(transcript,"exon.name", exon.getName());
				        						}
				            				if(i>= gene.getCdsEnd()) continue;
				            				if(i<  gene.getCdsStart()) break;
				            				
				            				if(wildRNA==null)
				        						{
				        						wildRNA=new StringBuilder();
				        						mutRNA=new MutedSequence(wildRNA);
				        						}
				            				
				            				if(i==position0)
				        						{
				            					append(transcript,"type", "EXON");
				            					position_in_cdna=wildRNA.length();
				        						append(transcript,"pos.cdna",String.valueOf(position_in_cdna));
				        						//in splicing ?
				        						if(exon.isSplicing(position0))
				        							{
				        							append(transcript,"splicing", "INTRON_SPLICING");
				        							
				        							if(exon.isSplicingAcceptor(position0))
				        								{
				        								append(transcript,"splicing", "INTRON_SPLICING_ACCEPTOR");
				        								}
				        							else  if(exon.isSplicingDonor(position0))
				        								{
				        								append(transcript,"splicing", "INTRON_SPLICING_DONOR");
				        								}
				        							}
				        						
				        						
				        						mutRNA.mutations.put(
				        								position_in_cdna,
				        								complement(alt.charAt(0))
				        								);
				        						}
				            				
				            				wildRNA.append(complement(localGenomicSeq.charAt(i)));
				            				if( wildRNA.length()%3==0 &&
				            					wildRNA.length()>0 &&
				            					wildProt==null)
					            				{
					            				wildProt=new ProteinCharSequence(geneticCode,wildRNA);
					            				mutProt=new ProteinCharSequence(geneticCode,mutRNA);
					            				}
				            				
				            				}
				            			
				            			KnownGene.Intron intron= exon.getPrevIntron();
				            			if(intron!=null &&
				            				intron.contains(position0))
				            				{
				            				append(transcript,"intron.name",intron.getName());
				            				append(transcript,"type", "INTRON");
				            				
				            				if(intron.isSplicing(position0))
				        						{
				            					append(transcript,"splicing", "INTRON_SPLICING");
				        						if(intron.isSplicingAcceptor(position0))
				        							{
				        							append(transcript,"splicing", "INTRON_SPLICING_ACCEPTOR");
				        							}
				        						else if(intron.isSplicingDonor(position0))
				        							{
				        							append(transcript,"splicing", "INTRON_SPLICING_DONOR");
				        							}
				        						}
				            				}
				            			--exon_index;
				            			}
				            		}
			            		}//end of if reverse
			        		
			        		if(insert_sequences && wildRNA!=null && mutRNA!=null)
			        			{
			        			append(transcript,"wild.cdna",wildRNA.toString());
			        			append(transcript,"mut.cdna",wildRNA.toString());
			        			}
			        		
			        		if( wildProt!=null &&
			        			mutProt!=null && 
			        			position_in_cdna>=0)
				    			{
			            		int pos_aa=position_in_cdna/3;
			            		int mod= position_in_cdna%3;
			            		
			            		
			            		if(insert_sequences)
				        			{
				        			append(transcript,"wild.prot",wildProt.toString());
				        			append(transcript,"mut.prot",mutProt.toString());
				        			}
			            		
			            		
			            		append(transcript,"wild.codon",""+
			            			wildRNA.charAt(position_in_cdna-mod+0)+
			            			wildRNA.charAt(position_in_cdna-mod+1)+
			            			wildRNA.charAt(position_in_cdna-mod+2)
			            			);
			            		append(transcript,"mut.codon",""+
			            			mutRNA.charAt(position_in_cdna-mod+0)+
			            			mutRNA.charAt(position_in_cdna-mod+1)+
			            			mutRNA.charAt(position_in_cdna-mod+2)
			            			);
			            		append(transcript,"position.protein",String.valueOf(pos_aa+1));
			            		append(transcript,"wild.aa",String.valueOf(wildProt.charAt(pos_aa)));
			            		append(transcript,"mut.aa",String.valueOf(mutProt.charAt(pos_aa)));
				    			if(isStop(wildProt.charAt(pos_aa)) &&
				    			   !isStop(mutProt.charAt(pos_aa)))
				    				{
				    				append(transcript,"type", "EXON_STOP_LOST");
				    				}
				    			else if( !isStop(wildProt.charAt(pos_aa)) &&
				    				 isStop(mutProt.charAt(pos_aa)))
				    				{
				    				append(transcript,"type", "EXON_STOP_GAINED");
				    				}
				    			else if(wildProt.charAt(pos_aa)==mutProt.charAt(pos_aa))
				    				{
				    				append(transcript,"type", "EXON_CODING_SYNONYMOUS");
				    				}
				    			else
				    				{
				    				append(transcript,"type", "EXON_CODING_NON_SYNONYMOUS");
				    				}
				    			}
			        		}//end of simpe ATCG
		            	else
		            		{
			        		Integer wildrna=null;
			        		int position_in_cdna=-1;
			        		
			        		
			        		
			        		if(gene.isForward())
			            		{
			            		if(position0 < gene.getCdsStart())
			            			{
			            			append(transcript,"type", "UTR5");
			            			}
			            		else if( gene.getCdsEnd()<= position0 )
			            			{
			            			append(transcript,"type", "UTR3");
			            			}
			            		else
				            		{
				            		int exon_index=0;
				            		while(exon_index< gene.getExonCount())
				            			{
				            			KnownGene.Exon exon= gene.getExon(exon_index);
				            			for(int i= exon.getStart();
				            					i< exon.getEnd();
				            					++i)
				            				{
				            				if(i==position0)
				        						{
				        						append(transcript,"exon.name", exon.getName());
				        						}
				            				if(i< gene.getCdsStart()) continue;
				            				if(i>= gene.getCdsEnd() ) break;
				        					
				        					if(wildrna==null)
				        						{
				        						wildrna=0;
				        						}
				        					
				        					if(i==position0)
				        						{
				        						append(transcript,"type", "EXON");
				        						append(transcript,"exon.name",exon.getName());
				        						position_in_cdna=wildrna;
				        						append(transcript,"pos.cdna", String.valueOf(wildrna));
				        						//in splicing ?
				        						if(exon.isSplicing(position0))
				        							{
				        							append(transcript,"type", "SPLICING");
				        							
				        							if(exon.isSplicingAcceptor(position0))
				        								{
				        								append(transcript,"type", "SPLICING_ACCEPTOR");
				        								}
				        							else  if(exon.isSplicingDonor(position0))
				        								{
				        								append(transcript,"type", "SPLICING_DONOR");
				        								}
				        							}
				        						}
				        					
				        					wildrna++;
				            				}
				            			KnownGene.Intron intron= exon.getNextIntron();
				            			if(intron!=null && intron.contains(position0))
				            				{
				            				append(transcript,"intron.name",intron.getName());
				            				append(transcript,"type", "INTRON");
				            				
				            				if(intron.isSplicing(position0))
				        						{
				            					append(transcript,"type", "INTRON_SPLICING");
				        						if(intron.isSplicingAcceptor(position0))
				        							{
				        							append(transcript,"type", "INTRON_SPLICING_ACCEPTOR");
				        							}
				        						else if(intron.isSplicingDonor(position0))
				        							{
				        							append(transcript,"type", "INTRON_SPLICING_DONOR");
				        							}
				        						}
				            				}
				            			++exon_index;
				            			}
				            		}
			            		}
			            	else // reverse orientation
			            		{
			            	
			            		if(position0 < gene.getCdsStart())
			            			{
			            			append(transcript,"type", "UTR3");
			            			}
			            		else if( gene.getCdsEnd()<=position0 )
			            			{
			            			append(transcript,"type", "UTR5");
			            			}
			            		else
				            		{
				            		
				            		int exon_index = gene.getExonCount()-1;
				            		while(exon_index >=0)
				            			{
				            			KnownGene.Exon exon= gene.getExon(exon_index);
				            			for(int i= exon.getEnd()-1;
				            				    i>= exon.getStart();
				            				--i)
				            				{
				            				if(i==position0)
				        						{
				            					append(transcript,"exon.name", exon.getName());
				        						}
				            				if(i>= gene.getCdsEnd()) continue;
				            				if(i<  gene.getCdsStart()) break;
				            				
				            				if(wildrna==null)
				        						{
				        						wildrna=0;
				        						}
				            				
				            				if(i==position0)
				        						{
				            					append(transcript,"type", "EXON");
				            					position_in_cdna=wildrna;
				        						append(transcript,"pos.cdna",String.valueOf(position_in_cdna));
				        						//in splicing ?
				        						if(exon.isSplicing(position0))
				        							{
				        							append(transcript,"type", "INTRON_SPLICING");
				        							
				        							if(exon.isSplicingAcceptor(position0))
				        								{
				        								append(transcript,"type", "INTRON_SPLICING_ACCEPTOR");
				        								}
				        							else  if(exon.isSplicingDonor(position0))
				        								{
				        								append(transcript,"type", "INTRON_SPLICING_DONOR");
				        								}
				        							}
				        						}
				            				
				            				wildrna++;
				            				}
				            			
				            			KnownGene.Intron intron= exon.getPrevIntron();
				            			if(intron!=null &&
				            				intron.contains(position0))
				            				{
				            				append(transcript,"intron.name",intron.getName());
				            				append(transcript,"type", "INTRON");
				            				
				            				if(intron.isSplicing(position0))
				        						{
				            					append(transcript,"type", "INTRON_SPLICING");
				        						if(intron.isSplicingAcceptor(position0))
				        							{
				        							append(transcript,"type", "INTRON_SPLICING_ACCEPTOR");
				        							}
				        						else if(intron.isSplicingDonor(position0))
				        							{
				        							append(transcript,"type", "INTRON_SPLICING_DONOR");
				        							}
				        						}
				            				}
				            			--exon_index;
				            			}
				            		}
			            		}//end of if reverse
			        		if( wildrna!=null &&
			        			position_in_cdna>=0)
				    			{
			            		int pos_aa=position_in_cdna/3;
			            		append(transcript,"position.protein",String.valueOf(pos_aa+1));
				    			}
		            		}//end of not simple ATGC
						
		            	//append(transcript,"geneSymbol", gene.getGeneSymbol());
						
			
						
						}
				}
		return dom;
		}
	
	private static void append(Element root,String name,String content)
		{
		Element e=root.getOwnerDocument().createElement(name);
		root.appendChild(e);
		e.appendChild(root.getOwnerDocument().createTextNode(content));
		}
	
	private static char complement(char c)
		{
		switch(c)
			{
			case 'A': case 'a': return 'T';
			case 'T': case 't': return 'A';
			case 'G': case 'g': return 'C';
			case 'C': case 'c': return 'G';
			}
		return '?';
		}
	private boolean isStop(char c)
		{
		return !Character.isLetter(c);
		}
	
	@Override
	public void destroy()
		{
		this.threadSafeGenomicSeq=null;
		super.destroy();
		}
	}
